/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc.method.annotation;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.ControllerAdviceBean;

/**
 * Invokes {@link RequestBodyAdvice} and {@link ResponseBodyAdvice} where each
 * instance may be (and is most likely) wrapped with
 * {@link org.springframework.web.method.ControllerAdviceBean ControllerAdviceBean}.
 *
 * @author Rossen Stoyanchev
 * @author Sam Brannen
 * @since 4.2
 */
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {

	private final List<Object> requestBodyAdvice = new ArrayList<Object>(4);

	private final List<Object> responseBodyAdvice = new ArrayList<Object>(4);


	/**
	 * Create an instance from a list of objects that are either of type
	 * {@code RequestBodyAdvice} or {@code ResponseBodyAdvice}.
	 */
	public RequestResponseBodyAdviceChain(List<Object> requestResponseBodyAdvice) {
		initAdvice(requestResponseBodyAdvice);
	}

	private void initAdvice(List<Object> requestResponseBodyAdvice) {
		if (requestResponseBodyAdvice == null) {
			return;
		}
		for (Object advice : requestResponseBodyAdvice) {
			// 获取增强器的bean类型
			Class<?> beanType = (advice instanceof ControllerAdviceBean ?
					((ControllerAdviceBean) advice).getBeanType() : advice.getClass());
			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
				// 如果beanType是RequestBodyAdvice的实现类，则添加到请求体增强器列表中
				this.requestBodyAdvice.add(advice);
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				// 如果beanType是ResponseBodyAdvice的实现类，则添加到响应体体增强器列表中
				this.responseBodyAdvice.add(advice);
			}
		}
	}

	private List<Object> getAdvice(Class<?> adviceType) {
		if (RequestBodyAdvice.class == adviceType) {
			return this.requestBodyAdvice;
		}
		else if (ResponseBodyAdvice.class == adviceType) {
			return this.responseBodyAdvice;
		}
		else {
			throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
		}
	}


	@Override
	public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
		throw new UnsupportedOperationException("Not implemented");
	}

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		throw new UnsupportedOperationException("Not implemented");
	}

	@Override
	// 处理空消息体，参数body为null
	public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		// 与beforeBodyRead方法逻辑相同
		for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
			if (advice.supports(parameter, targetType, converterType)) {
				// 链式执行所有增强器的handleEmptyBody，后一个增强器的body参数是前一个增强器的返回值，第一个增强器处理的body是null，但后续的body就不一定是null了
				body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
			}
		}
		return body;
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		// 遍历全部与parameter和RequestBodyAdvice.class匹配的RequestBodyAdvice
		for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
			// 通过RequestBody增强器的supports方法判断是否支持
			if (advice.supports(parameter, targetType, converterType)) {
				// 如果支持，则执行其beforeBodyRead增强方法，request变量替换成执行增强方法后的结果
				request = advice.beforeBodyRead(request, parameter, targetType, converterType);
				// 执行完成后继续遍历下一个增强器，所以这种类型的增强器整体是链式执行的，所有增强器只要supports条件满足，即会被执行
			}
		}
		// 返回被增强器替换后的request
		return request;
	}

	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		// 与beforeBodyRead方法逻辑相同
		for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
			if (advice.supports(parameter, targetType, converterType)) {
				// 链式执行所有增强器的afterBodyRead，后一个增强器的body参数是前一个增强器的返回值
				body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
			}
		}
		// 返回增强后的结果作为消息转换器的结果使用
		return body;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {
		// 调用处理body方法
		return processBody(body, returnType, contentType, converterType, request, response);
	}

	@SuppressWarnings("unchecked")
	private <T> Object processBody(Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {
		// 同RequestBodyAdvice增强逻辑相同，查找ResponseBodyAdvice增强器
		for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
			// 如果响应体增强器支持该返回类型和消息转换器类型
			if (advice.supports(returnType, converterType)) {
				// 则执行该增强器的增强方法，body取增强器的返回值，可以构造新的返回值或者对参数body进行增强。链式执行，后一个增强器传入的body参数是前一个增强器的返回值
				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType, contentType, converterType, request, response);
			}
		}
		// 返回增强后的body结果
		return body;
	}

	@SuppressWarnings("unchecked")
	private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
		// 获取adviceType类型的增强器列表，如果是RequestBodyAdvice，则返回requestBodyAdvice列表；如果是ResponseBodyAdvice，则返回responseBodyAdvice列表
		List<Object> availableAdvice = getAdvice(adviceType);
		// 如果列表为空，则返回空列表
		if (CollectionUtils.isEmpty(availableAdvice)) {
			return Collections.emptyList();
		}
		// 用于保存匹配结果的列表
		List<A> result = new ArrayList<A>(availableAdvice.size());
		// 遍历根据增强器类型获取的可用增强器列表
		for (Object advice : availableAdvice) {
			// 如果增强器实例为ControllerAdviceBean，说明只是增强器的ControllerAdviceBean封装，需要从封装中获取最终使用的增强器bean实例
			if (advice instanceof ControllerAdviceBean) {
				// 强转ControllerAdviceBean类型
				ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
				// ControllerAdviceBean封装中包含了通过@ControllerAdvice注解属性判断是否可以被应用到参数方法所在类的逻辑，如果不可以则继续遍历
				if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
					continue;
				}
				// 判断可以被应用，则从封装中解析bean实例
				advice = adviceBean.resolveBean();
			}
			// 如果解析后的bean实例是参数中需要的增强类型的实现类，则添加到结果中
			if (adviceType.isAssignableFrom(advice.getClass())) {
				result.add((A) advice);
			}
		}
		return result;
	}

}
